/*
 * control.cpp
 *
 * Módulo responsável pela base no envio e no recebimento das mensagens. A idéia
 * do projeto é ter métodos que agilizem o envio e o recebimento das informações.
 * Por isso, existem funções do tipo sendUntilZ e receiveUntilZ.
 *
 * v. 1.5.0
 *
 */

#include "control.h"

FlowControl::FlowControl(char *DEVICE)
{
    this->rs = new RawSocket(DEVICE);
}

/*
 * Envia uma mensagem de acordo com os dados e o tipo.
 * Para evitar o uso de diversos timeouts e para que a sincronização do servidor
 * e do cliente fiquem corretas, a função envia uma mensagem e já esperar por uma
 * resposta. Isso é bom, pois agiliza os processos para o cliente e o servidor.
 *
 * Vide exemplo do comando l:
 *     Envia uma solicitação para o server e fica esperando até chegar um X. Caso
 *     não chegue em um determinado tempo, envia novamente a mensagem. E assim por
 *     diante.
 * 
 * @data: os dados a serem enviados;
 * @buffer: um pequeno buffer para guardar os dados da resposta obtida.
 * @typeAnswer: tipo de resposta a ser obtida.
 * @waitAnswer: bool que indica se é ou não necessário esperar por uma resposta.
 * @sendAck: ao receber uma resposta (waintAnswer = true), esse bool indica se
 *           é ou necessário enviar um ACK.
 */
bool FlowControl::send(message *data, message *buffer, byte typeAnswer, bool waitAnswer,
                        bool sendAck)
{
    message *temp;
    int r = 0, resultTimeOut = 0;
    bool sendMessage = true, received = false;

    if(waitAnswer){
        int i;

        /* Tenta enviar mensagem até receber resposta ou o número de tentativas
         * ser igual MAX_TRY */
        for( i=0 ; i < MAX_TRY && !received ; i++ ){
            if (sendMessage) this->rs->sendMessage(data);
            resultTimeOut = this->waitTimeOut();
            sendMessage = true;
                                
            if (resultTimeOut){
                if ( !(temp = this->rs->getMessage(&r))){
                    switch (r){
                        case 1:
                            this->sendNack();
                            break;
                        case 2:
                            i--;
                            break;
                    }
                    r = 0;
                    sendMessage = false; 
                } else {
                    if (temp->getType() == typeAnswer){
                        if (buffer) *buffer = message((byte *)temp->getMessage());
                        if (sendAck) this->sendAck();
                        received = true;
                    } else if (temp->getType() == 'N')
                        return this->send(data, buffer, typeAnswer, waitAnswer, sendAck);
                }
            }
        }
        
        if(i == MAX_TRY && !received)
            return false;
    }
    else
        this->rs->sendMessage(data);

    return true;
}

/*
 * Recebe todas as informações e já envia um ACK para cada uma recebida. É a função
 * mais simples desse módulo, pois não se procupa com o que deve ser recebido.
 *
 * @buffer: buffer para armazenamento das respostas obtidas.
 */
bool FlowControl::receive(message *buffer, bool sendAck)
{
    message *temp;
    int r = 0, resultTimeOut, i;
    bool received = false;
    
    for (i = 0; i < MAX_TRY && !received; i++){
        resultTimeOut = this->waitTimeOut();

        if (resultTimeOut){
            r = 0;
            temp = this->rs->getMessage(&r);
            
            switch(r){
                case 0:
                     received = true;
                     *buffer = message((byte *)temp->getMessage());
                     break;
                case 1:
                    this->sendNack();
                    break;
                case 2:
                    i--;
                    break;
            }
        }
    }

    if( i == MAX_TRY && !received )
        return false;

    if (sendAck)
        this->sendAck();
    
    return true;
}

/*
 * Recebe um determinado tipo de resposta em um determinado tempo (timeout)
 * 
 * @typeAnswer: tipo de resposta que este método deve esperar.
 * @buffer: grava as informações obtidas no recebimento.
 */
bool FlowControl::receive(message *buffer, byte typeAnswer, bool sendAck)
{
    int  i = 0;
    bool received = false;
    
    do {
        i++;
        
        if ( !this->receive(buffer, sendAck))
            return false;

        if ((buffer->getType() == 'E' || buffer->getType() == 'Y' || buffer->getType() == 'N')
                && typeAnswer == 'V'){
            received = true;
        } else if (buffer->getType() == typeAnswer)
            received = true;
    } while(i < MAX_TRY && !received);

    if(i == MAX_TRY && !received)
        return false;

    return true;
}

/*
 * Para os dois métodos seguintes (sendUntilZ):
 * 
 * Envia diversas mensagens. Esse método agiliza o processo do cliente e do servidor.
 * Quando um desses necessita enviar mais dados do que o tamanho de um pacote, este
 * quebra as mensagens e envia sincronizadamente as mensagens junto com o receiveUntilZ.
 *
 * Idéia central:
 *      Carrega um pacote com as informações, envia e espera por um ACK - o envio
 *      dessa informação é de inteira responsabilidade do método send, então 
 *      simplesmente o método chama o send e faz com que este espere por um ACK.
 *      Detalhes de timeout são implementados no método send.
 *      Assim, o método segue o esquema: quebra, envia, espera resposta, quebra, 
 *      envia, espera resposta ...
 *      Ao terminar o envio dos pacotes, o método envia um 'Z'.
 * 
 * Através do polimorfismo, essa função consegue enviar informações tanto de uma
 * lista - classe List, quanto de um arquivo - classe fileManagement.
 */

/*
 * @data: informações a serem enviadas
 * @typeMessage: tipo de mensagem a ser enviada.
 * @typeAnswer: tipo de resposta a ser obtida.
 */
bool FlowControl::sendUntilZ(fileManagement *data, byte typeMessage, byte typeAnswer)
{
    message *msg = new message();
    unsigned short seq = 0;

    do {
        msg->setMessage(seq, typeMessage, (byte *)data->currentData(),
                        (size_t)data->currentDataSize());

        if ( !this->send(msg, NULL, typeAnswer, true, false))
            return false;
        
        seq = (seq+1)%7;
    } while(data->nextData());
    
    msg->setMessage(0, 'Z', NULL, 0);
    
    if ( !this->send(msg, NULL, 'Y', true, false))
        return false;
    
    delete msg;

    return true;
}

/*
 * @data: informações a serem enviadas
 * @size: tamanho total
 * @typeMessage: tipo de mensagem a ser enviada.
 * @typeAnswer: tipo de resposta a ser obtida.
 */
bool FlowControl::sendUntilZ(byte *data, unsigned long int size, byte typeMessage,
                        byte typeAnswer)
{
    unsigned long int sizeT, controlData = 0;
    message *msg = new message();
    unsigned short seq = 0;
    
    while(size > 0){
        sizeT = (MAX_DATA_SIZE > size)? size: MAX_DATA_SIZE;
        msg->setMessage(seq, typeMessage, &data[controlData], sizeT);

        if ( !this->send(msg, NULL, typeAnswer, true, false))
            return false;
        
        size -= sizeT;
        controlData += sizeT;
        seq = (seq+1)%7;
    }

    msg->setMessage(0, 'Z', NULL, 0);
    if ( !this->send(msg, NULL, 'Y', true, false))
        return false;

    delete msg;

    return true;
}

/*
 * Recebe diversas mensagens de um determinado tipo. Essa mensagens vão sendo
 * guardadas em uma lista. Pára quando recebe um 'Z'
 * 
 * @buffer: lista onde as informações recebidas devem ser guardadas.
 * @typeReceived: tipo de pacto a ser recebido.
 * @seq: início da sequencia de mensagens - padrão 0.
 */
bool FlowControl::receiveUntilZ(List *buffer, byte typeReceived, unsigned short seq)
{
    message *msg = new message();
    bool st = true;
    
    do {
        if ( !this->receive(msg))
            return false;
        if (msg->getType() != 'Z'){
            if (msg->getSeq() == seq && msg->getType() == typeReceived){
                buffer->copy(msg->getData(), msg->getSize()-2);
                seq = (seq+1)%7;
            }
        } else
            st = false;
    } while (st);

    delete msg;

    return true;
}

RawSocket * FlowControl::getRsDescriptor()
{
    return this->rs;
}
 
void FlowControl::sendAck()
{
    message msg;
    msg.setMessage(0, 'Y', NULL, 0);
    this->rs->sendMessage(&msg);
}

void FlowControl::sendNack()
{
    message msg;
    msg.setMessage(0, 'N', NULL, 0);
    this->rs->sendMessage(&msg);
}

int FlowControl::waitTimeOut()
{
    struct timeval timeOut;
    fd_set rfds;

    timeOut.tv_sec = TIME_OUT;
    timeOut.tv_usec = 0;

    FD_ZERO(&rfds);
    FD_SET(this->rs->getFileDescriptor(), &rfds);

    return select(this->rs->getFileDescriptor()+1, &rfds, NULL, NULL, &timeOut);
}
